package cn.lbd.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

/**
 * （1）打开ServerSocketChannel，监听客户端连接
 * （2）绑定监听端口，设置连接为非阻塞模式
 * （3）创建Reactor线程，创建多路复用器并启动线程
 * （4）将ServerSocketChannel注册到Reactor线程中的Selector上，监听ACCEPT事件
 * （5）Selector轮询准备就绪的key
 * （6）Selector监听到新的客户端接入，处理新的接入请求，完成TCP三次握手，简历物理链路
 * （7）设置客户端链路为非阻塞模式
 * （8）将新接入的客户端连接注册到Reactor线程的Selector上，监听读操作，读取客户端发送的网络消息
 * （9）异步读取客户端消息到缓冲区
 * （10）对Buffer编解码，处理半包消息，将解码成功的消息封装成Task
 * （11）将应答消息编码为Buffer，调用SocketChannel的write将消息异步发送给客户端
 */
public class Server implements Runnable {

    private Selector selector;
    //分配一个新的字节缓冲区
    private ByteBuffer readBuffer = ByteBuffer.allocate(1024);
    private ByteBuffer writeBuffer = ByteBuffer.allocate(1024);

    public Server(int port) {
        try {
            //打开多路复用器
            this.selector = Selector.open();
            //打开服务器通道
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            //设置服务器通道模式为非阻塞
            serverSocketChannel.configureBlocking(false);
            //绑定监听端口
            //InetSocketAddress：用于绑定，连接或返回值的套接字所使用的不可变对象，通配符是一个特殊的本地IP地址。 通常意味着“任何”，只能用于bind操作。
            serverSocketChannel.bind(new InetSocketAddress(port));
            //将服务器通道注册到多路复用器，为通道分配key,取得时候通过key找对应通道
            //选择器去监控serverSocketChannel的阻塞状态
            //注册时不止监听一个事件，可以用 | 分割，SelectionKey.OP_ACCEPT|SelectionKey.OP_READ
            serverSocketChannel.register(this.selector, SelectionKey.OP_ACCEPT);
            System.out.println("Server start , port = " + port);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while (true) {
            try {
                //多路复用器开启监听
                // 阻塞等待事件
                this.selector.select();
                //返回所有注册在改多路选择器上的通道的key的集合的迭代器  =>事件列表
                Iterator<SelectionKey> keys = this.selector.selectedKeys().iterator();
                while (keys.hasNext()) {
                    SelectionKey key = keys.next();
                    keys.remove();
                    dispatch(key);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    //分发事件
    public void dispatch(SelectionKey key) {
        //如果事件有效
        if (key.isValid()) {
            //如果阻塞状态
            if (key.isAcceptable()) {
                this.accept(key); //ServerSocketChannel
            }
            //如果可读状态
            if (key.isReadable()) {
                try {
                    this.read(key);  //SocketChannel
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            //如果可写状态
            if (key.isWritable()) {
                this.write(key);  //ServerSocketChannel
            }
        }
    }

    public void accept(SelectionKey key) {
        ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
        try {
            //执行阻塞方法
            SocketChannel channel = serverSocketChannel.accept();
            channel.configureBlocking(false);
            //注册到多路复用器，并设置为读取标识
            channel.register(this.selector, SelectionKey.OP_READ);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * 读
     *
     * @param key
     * @throws IOException
     */
    public void read(SelectionKey key) throws IOException {
        //清空缓冲区旧数据
        this.readBuffer.clear();
        //拿到通道对象
        SocketChannel channel = (SocketChannel) key.channel();
        int count = channel.read(this.readBuffer);
        //说明没读到信息,直接返回
        if (count == -1) {
            key.channel().close();
            key.cancel();
            return;
        }
        //读取之前需要进行复位(把position 和limit进行复位)
        //在通道读取或写入操作的序列之后，调用此方法来准备一系列通道写入或相对获取操作
        this.readBuffer.flip();
        //根据缓冲区数据长度创建相应大小byte[]  remaining：获取缓冲区大小
        byte[] bytes = new byte[this.readBuffer.remaining()];
        //接受缓冲区数据  将字节从此缓冲区传输到给定的目标数组
        this.readBuffer.get(bytes);
        //trim：删除任何前导和尾随空格
        String request = new String(bytes).trim();
        System.out.println("Server：" + request);
        this.writeBuffer.clear();
        //写回给客户端的响应数据
        writeBuffer.put(("服务端收到请求，数据为" + request).getBytes());
        //在通道读取或写入操作的序列之后，调用此方法来准备一系列通道写入或相对获取操作  把position 和limit进行复位
        writeBuffer.flip();
        //向通道写入字节
        channel.write(writeBuffer);

        /**
         * 对于读操作，缓冲区都是作为个中间件，读的数据最终放在byte[]，写的数据直接通过channel传出去
         * */
    }

    public void write(SelectionKey key) {
        ServerSocketChannel channel = (ServerSocketChannel) key.channel();
        try {
            channel.register(this.selector, SelectionKey.OP_WRITE);
        } catch (ClosedChannelException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        new Thread(new Server(8765)).start();
    }
}
